﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace DesdemonaDespairApp.Common
{
    /// <summary>
    /// Typical implementation of Page that provides several important conveniences:
    /// <list type="bullet">
    /// <item>
    /// <description>Application view state to visual state mapping</description>
    /// </item>
    /// <item>
    /// <description>GoBack, GoForward, and GoHome event handlers</description>
    /// </item>
    /// <item>
    /// <description>Mouse and keyboard shortcuts for navigation</description>
    /// </item>
    /// <item>
    /// <description>State management for navigation and process lifetime management</description>
    /// </item>
    /// <item>
    /// <description>A default view model</description>
    /// </item>
    /// </list>
    /// </summary>
    [Windows.Foundation.Metadata.WebHostHidden]
    public class LayoutAwarePage : Page
    {
        /// <summary>
        /// Identifies the <see cref="DefaultViewModel"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty DefaultViewModelProperty =
            DependencyProperty.Register( "DefaultViewModel", typeof( IObservableMap<String, Object> ),
            typeof( LayoutAwarePage ), null );

        private List<Control> _layoutAwareControls;

        /// <summary>
        /// Initializes a new instance of the <see cref="LayoutAwarePage"/> class.
        /// </summary>
        public LayoutAwarePage()
        {
            if( Windows.ApplicationModel.DesignMode.DesignModeEnabled ) return;

            // Create an empty default view model
            this.DefaultViewModel = new ObservableDictionary<String, Object>();

            // When this page is part of the visual tree make two changes:
            // 1) Map application view state to visual state for the page
            // 2) Handle keyboard and mouse navigation requests
            this.Loaded += ( sender, e ) =>
            {
                this.StartLayoutUpdates( sender, e );

                // Keyboard and mouse navigation only apply when occupying the entire window
                if( this.ActualHeight == Window.Current.Bounds.Height &&
                    this.ActualWidth == Window.Current.Bounds.Width )
                {
                    // Listen to the window directly so focus isn't required
                    Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
                        CoreDispatcher_AcceleratorKeyActivated;
                    Window.Current.CoreWindow.PointerPressed +=
                        this.CoreWindow_PointerPressed;
                }
            };

            // Undo the same changes when the page is no longer visible
            this.Unloaded += ( sender, e ) =>
            {
                this.StopLayoutUpdates( sender, e );
                Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
                    CoreDispatcher_AcceleratorKeyActivated;
                Window.Current.CoreWindow.PointerPressed -=
                    this.CoreWindow_PointerPressed;
            };
        }

        /// <summary>
        /// An implementation of <see cref="IObservableMap&lt;String, Object&gt;"/> designed to be
        /// used as a trivial view model.
        /// </summary>
        protected IObservableMap<String, Object> DefaultViewModel
        {
            get
            {
                return this.GetValue( DefaultViewModelProperty ) as IObservableMap<String, Object>;
            }

            set
            {
                this.SetValue( DefaultViewModelProperty, value );
            }
        }

        #region Navigation support

        /// <summary>
        /// Invoked as an event handler to navigate backward in the page's associated
        /// <see cref="Frame"/> until it reaches the top of the navigation stack.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="e">Event data describing the conditions that led to the event.</param>
        protected virtual void GoHome( object sender, RoutedEventArgs e )
        {
            // Use the navigation frame to return to the topmost page
            if( this.Frame != null )
            {
                while( this.Frame.CanGoBack ) this.Frame.GoBack();
            }
        }

        /// <summary>
        /// Invoked as an event handler to navigate backward in the navigation stack
        /// associated with this page's <see cref="Frame"/>.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="e">Event data describing the conditions that led to the
        /// event.</param>
        protected virtual void GoBack( object sender, RoutedEventArgs e )
        {
            // Use the navigation frame to return to the previous page
            if( this.Frame != null && this.Frame.CanGoBack ) this.Frame.GoBack();
        }

        /// <summary>
        /// Invoked as an event handler to navigate forward in the navigation stack
        /// associated with this page's <see cref="Frame"/>.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="e">Event data describing the conditions that led to the
        /// event.</param>
        protected virtual void GoForward( object sender, RoutedEventArgs e )
        {
            // Use the navigation frame to move to the next page
            if( this.Frame != null && this.Frame.CanGoForward ) this.Frame.GoForward();
        }

        /// <summary>
        /// Invoked on every keystroke, including system keys such as Alt key combinations, when
        /// this page is active and occupies the entire window.  Used to detect keyboard navigation
        /// between pages even when the page itself doesn't have focus.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="args">Event data describing the conditions that led to the event.</param>
        private void CoreDispatcher_AcceleratorKeyActivated( CoreDispatcher sender,
            AcceleratorKeyEventArgs args )
        {
            var virtualKey = args.VirtualKey;

            // Only investigate further when Left, Right, or the dedicated Previous or Next keys
            // are pressed
            if( ( args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
                args.EventType == CoreAcceleratorKeyEventType.KeyDown ) &&
                ( virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
                (int)virtualKey == 166 || (int)virtualKey == 167 ) )
            {
                var coreWindow = Window.Current.CoreWindow;
                var downState = CoreVirtualKeyStates.Down;
                bool menuKey = ( coreWindow.GetKeyState( VirtualKey.Menu ) & downState ) == downState;
                bool controlKey = ( coreWindow.GetKeyState( VirtualKey.Control ) & downState ) == downState;
                bool shiftKey = ( coreWindow.GetKeyState( VirtualKey.Shift ) & downState ) == downState;
                bool noModifiers = !menuKey && !controlKey && !shiftKey;
                bool onlyAlt = menuKey && !controlKey && !shiftKey;

                if( ( (int)virtualKey == 166 && noModifiers ) ||
                    ( virtualKey == VirtualKey.Left && onlyAlt ) )
                {
                    // When the previous key or Alt+Left are pressed navigate back
                    args.Handled = true;
                    this.GoBack( this, new RoutedEventArgs() );
                }
                else if( ( (int)virtualKey == 167 && noModifiers ) ||
                    ( virtualKey == VirtualKey.Right && onlyAlt ) )
                {
                    // When the next key or Alt+Right are pressed navigate forward
                    args.Handled = true;
                    this.GoForward( this, new RoutedEventArgs() );
                }
            }
        }

        /// <summary>
        /// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
        /// page is active and occupies the entire window.  Used to detect browser-style next and
        /// previous mouse button clicks to navigate between pages.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="args">Event data describing the conditions that led to the event.</param>
        private void CoreWindow_PointerPressed( CoreWindow sender,
            PointerEventArgs args )
        {
            var properties = args.CurrentPoint.Properties;

            // Ignore button chords with the left, right, and middle buttons
            if( properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
                properties.IsMiddleButtonPressed ) return;

            // If back or foward are pressed (but not both) navigate appropriately
            bool backPressed = properties.IsXButton1Pressed;
            bool forwardPressed = properties.IsXButton2Pressed;
            if( backPressed ^ forwardPressed )
            {
                args.Handled = true;
                if( backPressed ) this.GoBack( this, new RoutedEventArgs() );
                if( forwardPressed ) this.GoForward( this, new RoutedEventArgs() );
            }
        }

        #endregion

        #region Visual state switching

        /// <summary>
        /// Invoked as an event handler, typically on the <see cref="FrameworkElement.Loaded"/>
        /// event of a <see cref="Control"/> within the page, to indicate that the sender should
        /// start receiving visual state management changes that correspond to application view
        /// state changes.
        /// </summary>
        /// <param name="sender">Instance of <see cref="Control"/> that supports visual state
        /// management corresponding to view states.</param>
        /// <param name="e">Event data that describes how the request was made.</param>
        /// <remarks>The current view state will immediately be used to set the corresponding
        /// visual state when layout updates are requested.  A corresponding
        /// <see cref="FrameworkElement.Unloaded"/> event handler connected to
        /// <see cref="StopLayoutUpdates"/> is strongly encouraged.  Instances of
        /// <see cref="LayoutAwarePage"/> automatically invoke these handlers in their Loaded and
        /// Unloaded events.</remarks>
        /// <seealso cref="DetermineVisualState"/>
        /// <seealso cref="InvalidateVisualState"/>
        public void StartLayoutUpdates( object sender, RoutedEventArgs e )
        {
            var control = sender as Control;
            if( control == null ) return;
            if( this._layoutAwareControls == null )
            {
                // Start listening to view state changes when there are controls interested in updates
                Window.Current.SizeChanged += this.WindowSizeChanged;
                this._layoutAwareControls = new List<Control>();
            }
            this._layoutAwareControls.Add( control );

            // Set the initial visual state of the control
            VisualStateManager.GoToState( control, DetermineVisualState(), false );
        }

        private void WindowSizeChanged( object sender, WindowSizeChangedEventArgs e )
        {
            this.InvalidateVisualState();
        }

        /// <summary>
        /// Invoked as an event handler, typically on the <see cref="FrameworkElement.Unloaded"/>
        /// event of a <see cref="Control"/>, to indicate that the sender should start receiving
        /// visual state management changes that correspond to application view state changes.
        /// </summary>
        /// <param name="sender">Instance of <see cref="Control"/> that supports visual state
        /// management corresponding to view states.</param>
        /// <param name="e">Event data that describes how the request was made.</param>
        /// <remarks>The current view state will immediately be used to set the corresponding
        /// visual state when layout updates are requested.</remarks>
        /// <seealso cref="StartLayoutUpdates"/>
        public void StopLayoutUpdates( object sender, RoutedEventArgs e )
        {
            var control = sender as Control;
            if( control == null || this._layoutAwareControls == null ) return;
            this._layoutAwareControls.Remove( control );
            if( this._layoutAwareControls.Count == 0 )
            {
                // Stop listening to view state changes when no controls are interested in updates
                this._layoutAwareControls = null;
                Window.Current.SizeChanged -= this.WindowSizeChanged;
            }
        }

        /// <summary>
        /// Translates <see cref="ApplicationViewState"/> values into strings for visual state
        /// management within the page.  The default implementation uses the names of enum values.
        /// Subclasses may override this method to control the mapping scheme used.
        /// </summary>
        /// <param name="viewState">View state for which a visual state is desired.</param>
        /// <returns>Visual state name used to drive the
        /// <see cref="VisualStateManager"/></returns>
        /// <seealso cref="InvalidateVisualState"/>
        //protected virtual string DetermineVisualState( ApplicationViewState viewState )
        //{
        //    return viewState.ToString();
        //}


        protected virtual string DetermineVisualState()
        {
            string visualState = "FullScreenLandscape";
            var windowWidth = Window.Current.Bounds.Width;
            var windowHeight = Window.Current.Bounds.Height;

            if( windowWidth <= 500 )
            {
                visualState = "Snapped" + "_Detail";
            }
            else if( windowWidth <= 1366 )
            {
                if( windowWidth < windowHeight )
                {
                    visualState = "FullScreenPortrait" + "_Detail";
                }
                else
                {
                    visualState = "FilledOrNarrow";
                }

            }

            return visualState;

            // Determine visual states for landscape layouts based not on the view state, but
            // on the width of the window.  This page has one layout that is appropriate for
            // 1366 virtual pixels or wider, and another for narrower displays or when a snapped
            // application reduces the horizontal space available to less than 1366.
            //if( viewState == ApplicationViewState.Filled ||
            //    viewState == ApplicationViewState.FullScreenLandscape )
            //{
            //    var windowWidth = Window.Current.Bounds.Width;
            //    if( windowWidth >= 1366 ) return "FullScreenLandscapeOrWide";
            //    return "FilledOrNarrow";
            //}

            // Original ApplicationViewState values
            //"FullScreenLandscape"
            //"Filled"
            //"Snapped" 
            //"FullScreenPortrait" 
        }


        /// <summary>
        /// Updates all controls that are listening for visual state changes with the correct
        /// visual state.
        /// </summary>
        /// <remarks>
        /// Typically used in conjunction with overriding <see cref="DetermineVisualState"/> to
        /// signal that a different value may be returned even though the view state has not
        /// changed.
        /// </remarks>
        public void InvalidateVisualState()
        {
            if( this._layoutAwareControls != null )
            {
                string visualState = DetermineVisualState();
                foreach( var layoutAwareControl in this._layoutAwareControls )
                {
                    VisualStateManager.GoToState( layoutAwareControl, visualState, false );
                }
            }
        }

        #endregion

        #region Process lifetime management

        private String _pageKey;

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property provides the group to be displayed.</param>
        protected override void OnNavigatedTo( NavigationEventArgs e )
        {
            // Returning to a cached page through navigation shouldn't trigger state loading
            if( this._pageKey != null ) return;

            var frameState = SuspensionManager.SessionStateForFrame( this.Frame );
            this._pageKey = "Page-" + this.Frame.BackStackDepth;

            if( e.NavigationMode == NavigationMode.New )
            {
                // Clear existing state for forward navigation when adding a new page to the
                // navigation stack
                var nextPageKey = this._pageKey;
                int nextPageIndex = this.Frame.BackStackDepth;
                while( frameState.Remove( nextPageKey ) )
                {
                    nextPageIndex++;
                    nextPageKey = "Page-" + nextPageIndex;
                }

                // Pass the navigation parameter to the new page
                this.LoadState( e.Parameter, null );
            }
            else
            {
                // Pass the navigation parameter and preserved page state to the page, using
                // the same strategy for loading suspended state and recreating pages discarded
                // from cache
                this.LoadState( e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey] );
            }
        }

        /// <summary>
        /// Invoked when this page will no longer be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property provides the group to be displayed.</param>
        protected override void OnNavigatedFrom( NavigationEventArgs e )
        {
            var frameState = SuspensionManager.SessionStateForFrame( this.Frame );
            var pageState = new Dictionary<String, Object>();
            this.SaveState( pageState );
            frameState[_pageKey] = pageState;
        }

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="navigationParameter">The parameter value passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
        /// </param>
        /// <param name="pageState">A dictionary of state preserved by this page during an earlier
        /// session.  This will be null the first time a page is visited.</param>
        protected virtual void LoadState( Object navigationParameter, Dictionary<String, Object> pageState )
        {
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
        protected virtual void SaveState( Dictionary<String, Object> pageState )
        {
        }

        #endregion

        /// <summary>
        /// Implementation of IObservableMap that supports reentrancy for use as a default view
        /// model.
        /// </summary>
        private class ObservableDictionary<K, V> : IObservableMap<K, V>
        {
            private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<K>
            {
                public ObservableDictionaryChangedEventArgs( CollectionChange change, K key )
                {
                    this.CollectionChange = change;
                    this.Key = key;
                }

                public CollectionChange CollectionChange { get; private set; }
                public K Key { get; private set; }
            }

            private Dictionary<K, V> _dictionary = new Dictionary<K, V>();
            public event MapChangedEventHandler<K, V> MapChanged;

            private void InvokeMapChanged( CollectionChange change, K key )
            {
                var eventHandler = MapChanged;
                if( eventHandler != null )
                {
                    eventHandler( this, new ObservableDictionaryChangedEventArgs( change, key ) );
                }
            }

            public void Add( K key, V value )
            {
                this._dictionary.Add( key, value );
                this.InvokeMapChanged( CollectionChange.ItemInserted, key );
            }

            public void Add( KeyValuePair<K, V> item )
            {
                this.Add( item.Key, item.Value );
            }

            public bool Remove( K key )
            {
                if( this._dictionary.Remove( key ) )
                {
                    this.InvokeMapChanged( CollectionChange.ItemRemoved, key );
                    return true;
                }
                return false;
            }

            public bool Remove( KeyValuePair<K, V> item )
            {
                V currentValue;
                if( this._dictionary.TryGetValue( item.Key, out currentValue ) &&
                    Object.Equals( item.Value, currentValue ) && this._dictionary.Remove( item.Key ) )
                {
                    this.InvokeMapChanged( CollectionChange.ItemRemoved, item.Key );
                    return true;
                }
                return false;
            }

            public V this[K key]
            {
                get
                {
                    return this._dictionary[key];
                }
                set
                {
                    this._dictionary[key] = value;
                    this.InvokeMapChanged( CollectionChange.ItemChanged, key );
                }
            }

            public void Clear()
            {
                var priorKeys = this._dictionary.Keys.ToArray();
                this._dictionary.Clear();
                foreach( var key in priorKeys )
                {
                    this.InvokeMapChanged( CollectionChange.ItemRemoved, key );
                }
            }

            public ICollection<K> Keys
            {
                get { return this._dictionary.Keys; }
            }

            public bool ContainsKey( K key )
            {
                return this._dictionary.ContainsKey( key );
            }

            public bool TryGetValue( K key, out V value )
            {
                return this._dictionary.TryGetValue( key, out value );
            }

            public ICollection<V> Values
            {
                get { return this._dictionary.Values; }
            }

            public bool Contains( KeyValuePair<K, V> item )
            {
                return this._dictionary.Contains( item );
            }

            public int Count
            {
                get { return this._dictionary.Count; }
            }

            public bool IsReadOnly
            {
                get { return false; }
            }

            public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
            {
                return this._dictionary.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this._dictionary.GetEnumerator();
            }

            public void CopyTo( KeyValuePair<K, V>[] array, int arrayIndex )
            {
                int arraySize = array.Length;
                foreach( var pair in this._dictionary )
                {
                    if( arrayIndex >= arraySize ) break;
                    array[arrayIndex++] = pair;
                }
            }
        }
    }
}
